﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
using System.ComponentModel;
using System.Diagnostics;

namespace FolderData
{
    public class Character : Substance
    {
        #region Definitions
        public class CharacterDataSet : Substance.DataSet
        {
            public CharacterDataSet()
            {}
            protected CharacterKind _kind;
            public CharacterKind Kind
            { 
                get { return _kind; }
                set { _kind = value; }
            }

            public float Speed { get; set; }

            public override void Serialize(ContentWriter output)
            {
                base.Serialize(output);
                output.Write((int)Kind);
                output.Write(Speed);
            }
            public override void Deserialize(ContentReader input)
            {
                base.Deserialize(input);
                this.Kind = (CharacterKind)input.ReadInt32();
                this.Speed = input.ReadSingle();
            }
        }
        public enum Direction
        {
            /// <summary>
            /// 캐릭터가 왼쪽을 보고 있다.
            /// </summary>
            LEFT,
            /// <summary>
            /// 캐릭터가 오른쪽을 보고 있다.
            /// </summary>
            RIGHT
        }
        public enum State
        {
            IDLE    = 0,
            WALK    = 1,
            JUMP    = 2
        }
        public enum CharacterKind
        {
            PLAYER,
            MONSTER
        }
        public enum JumpState
        {
            TAKE_OFF    = 0,
            FLY         = 1,    
            LANDING     = 4,
            LANDED      = 5
        }
        public enum FlyState
        {
            Up          = 1,
            Normal      = 2,
            Down        = 4, //3번째 프레임 안예뻐서 안쓸래
        }
        #endregion

        #region Fields
        protected CharacterKind _kind;              //캐릭터의 종류
        protected string[] _spriteAssetNames;       //스프라이트 asset의 이름.
        protected string[] _inverseSpriteAssetNames;//색반전 스프라이트 asset 이름
        protected Texture2D[] _sprites;             //스프라이트는 한가지 스테이트에 한장의 스테이트. 즉 여러 그림이 한 장에 들어 있게 된다. 
                                                    //기본배치는 왼쪽0, 왼쪽1, 왼쪽2, ..., 오른쪽n, 오른쪽n-1, ..., 오른쪽0
                                                    //주의!! 오른쪽 스프라이트의 배치는 왼쪽과 반대이다.
        protected Texture2D[] _inverseSprites;      //색반전 스프라이트들
        protected Rectangle[] _frameSizes;          //x, y값은 무시 width, height만 사용됨.
        protected int[] _numFrames;
        protected int _curSprite;                   //현재 스프라이트 종류를 가리킴. (현재 이 캐릭터의 State에 따라 변하게 해야 한다.)
        protected Direction _curDirection = Direction.RIGHT;          //Assumption: 좌/우밖에 없다.
        protected int _curFrame;                    //현재 프레임번호.
        protected int[] _cfrDurations;              //고정 Frame Rate의 duration. 단위 ms
        protected int[][] _vfrDuratios;             //가변 Frame Rate의 duration. 단위 ms
        protected bool _isCFR;                      //프레임간 Duration이 일정한가? false이면 가변 Duration
        protected int _elapsedTime;                 //이번 프레임 동안 지속된 시간. 단위 ms
        protected bool _isMultiGeom;                //스테이트마다 다른 Geom과 Body, Origin을 사용할 것인가
        protected Body[] _bodys;                    //스테이트별 Body
        protected Geom[] _geoms;                    //스테이트별 Geom
        protected Vector2[] _origins;               //스테이트별 origin
        protected Vertices[] _vertsArray;           //스테이트별 Vertices
        protected Vertices[] _textureVertsArray;    //스테이트별 Vertices
        protected new State _state;                     //현재 스테이트.
        protected JumpState _jumpState;             //현재 점프 스테이트
        protected bool _flyDownFlag;                //점프에서 내려가고 있다는 표시. 
        protected bool _hasInverse;                 //색반전된 캐릭터를 가지고 있는가? (ex: 흰고양이)

        [ReadOnly(true)]
        public CharacterKind Kind
        {
            get { return _kind; }
            set { _kind = value; }
        }
        [ReadOnly(true)]
        public float Speed
        { get; set;}
        [Browsable(false)]
        public string[] SpriteAssetNames
        {
            get { return _spriteAssetNames; }
            set { _spriteAssetNames = value; }
        }
        [Browsable(false)]
        public string[] InverseSpriteAssetNames
        {
            get { return _inverseSpriteAssetNames; }
            set 
            {
                if (value != null && value.Length > 0)
                    _hasInverse = true;
                else
                    _hasInverse = false;
                _inverseSpriteAssetNames = value;
            }
        }
        [Browsable(false)]
        public Rectangle[] FrameSizes
        {
            get { return _frameSizes; }
            set { _frameSizes = value; }
        }
        [ReadOnly(true)]
        public bool IsCFR
        {
            get { return _isCFR; }
            set { _isCFR = value; }
        }
        /// <summary>
        /// 고정 프레임일 때 각 State별 duration (ms)
        /// </summary>
        [Browsable(false)]
        public int[] CFRDurations
        {
            get { return _cfrDurations; }
            set 
            {
                if (!IsCFR && value.Length != 0)
                    throw new ArgumentException("CFRDuration은 IsCFR이 true일 때만 가능합니다.");
                _cfrDurations = value; 
            }
        }
        /// <summary>
        /// 가변 프레임 일 때 각 State, 프레임 별 duration (ms)
        /// </summary>
        [Browsable(false)]
        public int[][] VFRDurations
        {
            get { return _vfrDuratios; }
            set 
            {
                if (IsCFR && value.Length != 0)
                    throw new ArgumentException("VFRDuration은 IsCFR이 false일 때만 가능합니다.");
                _vfrDuratios = value;
            }
        }
        [ReadOnly(true)]
        public bool IsMultiGeom
        {
            get { return _isMultiGeom; }
            set { _isMultiGeom = value; }
        }
        [Browsable(false)]
        public override Vertices Verts
        {
            get
            {
                return base.Verts;
            }
            set
            {
                if (_isMultiGeom && value.Count != 0)
                    throw new ArgumentException("Verts는 IsMultiGeom이 false일 때만 가능합니다.");
                base.Verts = value;
            }
        }
        [Browsable(false)]
        public Vertices[] VertsArray
        {
            get 
            {
                return _vertsArray;
            }
            set
            {
                if (!_isMultiGeom && value.Length != 0)
                    throw new ArgumentException("VertsArray는 IsMultiGeom이 true일 때만 가능합니다.");
                _vertsArray = value;
            }
        }
        [Browsable(false)]
        public Vertices[] TextureVertsArray
        {
            get
            {
                return _textureVertsArray;
            }
            set
            {
                _textureVertsArray = value;
            }
        }

        public override Texture2D Texture
        {
            get
            {
                if (InFoldMode)
                    return _inverseSprites[_curSprite];
                else
                    return _sprites[_curSprite];
            }
        }
        public override Rectangle? Rect
        {
            get
            {
                return new Rectangle((_curFrame + (_curDirection == Direction.LEFT ? 0 : _numFrames[_curSprite] / 2)) * _frameSizes[_curSprite].Width, 0, 
                    _frameSizes[_curSprite].Width, _frameSizes[_curSprite].Height);
            }
        }
        [Browsable(false)]
        protected int Duration
        {
            get
            {
                if (_isCFR)
                {
                    return _cfrDurations[_curSprite];
                }
                else
                {
                    return _vfrDuratios[_curSprite][_curFrame];
                }
            }
        }

        //collidedGeom. character의 jump판정과
        //monster의 방향 전환 충동 판정 등을 위해 사용된다.
        [Browsable(false)]
        [ContentSerializerIgnore]
        public List<Geom> CollidedGeom = new List<Geom>();
        [Browsable(false)]
        [ContentSerializerIgnore]
        public Direction CurDirection
        {
            get {return _curDirection;}
            set { _curDirection = value; }
        }
        [Browsable(false)]
        [ContentSerializerIgnore]
        public State CurState
        {
            get { return _state; }
            set
            {
                _state = ChangeState(value);
            }
        }
        [Browsable(false)]
        [ContentSerializerIgnore]
        public JumpState CurJumpState
        {
            get { return _jumpState; }
            set
            {
                ChangeJumpState(value);
            }
        }
        [Browsable(false)]
        [ContentSerializerIgnore]
        public bool HasInverse
        {
            get { return _hasInverse; }
            set { _hasInverse = value; }
        }
        [Browsable(false)]
        [ContentSerializerIgnore]
        public bool InFoldMode
        {
            get;
            set;
        }

        #endregion

        #region Methods
        private FlyState calculateFlyState()
        {
            if (_body.LinearVelocity.Y < -50f && !_flyDownFlag)
                return FlyState.Up;
            else if (_body.LinearVelocity.Y < 50f && !_flyDownFlag)
                return FlyState.Normal;
            else
            {
                _flyDownFlag = true;
                return FlyState.Down;
            }
        }

        /// <summary>
        /// 다음 프레임을 비춘다.
        /// </summary>
        protected void NextFrame()
        {
            if (_state == State.JUMP)
            {
                //TakeOff외에 Jump할 때의 프레임은 여기서 신경쓰지 않는다. 
                //chagneJumpState에서 신경쓴다.
                switch (_jumpState)
                {
                    case JumpState.TAKE_OFF:
                        ChangeJumpState(JumpState.FLY);
                        break;
                    case JumpState.LANDING:
                        ChangeJumpState(JumpState.LANDED);
                        break;
                    case JumpState.LANDED:
                        ChangeState(State.IDLE);
                        break;
                }
            }
            else
            {
                if (_numFrames[_curSprite] / 2 <= 1) //그림이 한 장이면 다음 프레임으로 이동하지 않는다.
                    return;
                ++_curFrame;
                _curFrame %= (_numFrames[_curSprite] / 2);
            }
        }

        public void ChangeJumpState(JumpState s)
        {
            Debug.Assert(_state != State.JUMP || _numFrames[_curSprite] / 2 == 6, "현재 점프관련 프레임이 6개일 때만 고려하고 있다.");
            _jumpState = s;
            _curFrame = (int)s;
            if (s == JumpState.FLY)
            {
                _flyDownFlag = false;
                FlyState st = calculateFlyState();
                _curFrame = (int)st;
            }
        }

        /// <summary>
        /// 바꿀 State에 해당하는 숫자를 입력하여 현재 상태를 바꾼다.
        /// Multiple geom과 body를 가지고 있다면 시뮬레이터에서 그 종류를 바꿔준다.
        /// </summary>
        /// <param name="stateNum">바꿀 State에 대응되는 정수값</param>
        protected State ChangeState(State s)
        {
            if (s != _state && _state != State.JUMP || _state == State.JUMP && _jumpState == JumpState.LANDED)
            {
                int stateNum = (int)s;
                _curFrame = 0;
                _curSprite = stateNum;
                _elapsedTime = 0;
                _textureVerts = _textureVertsArray[stateNum];
                if (_isMultiGeom)
                {
                    Vector2 oldPos = Position;
                    Vector2 oldOrigin = _origin;
                    _simulator.Remove(_body);
                    _simulator.Remove(_geom);
                    _body = _bodys[stateNum];
                    _geom = _geoms[stateNum];
                    _origin = _origins[stateNum];
                    _simulator.Add(_body);
                    _simulator.Add(_geom);
                    _verts = _vertsArray[stateNum];
                    _textureVerts = _textureVertsArray[stateNum];
                    //새로운 Body의 위치를 잡아준다. 이 위치는 두 body 사이의 origin 차이를 보정해서 잡아준다.
                    _body.Position = new Vector2(oldPos.X + _origin.X - oldOrigin.X, oldPos.Y + _origin.Y - oldOrigin.Y);
                }
                if (s == State.JUMP)
                {
                    ChangeJumpState(JumpState.TAKE_OFF);
                }
                return s;
            }
            return _state;
        }

        /// <summary>
        /// 매 프레임 불려서 Character의 상태를 업데이트 함.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            if (InFoldMode) //pause updaing
                return;

            _elapsedTime += gameTime.ElapsedGameTime.Milliseconds;
            if (_elapsedTime > Duration)
            {
                NextFrame();
                _elapsedTime = 0;
            }

            //임시 패치. 접어서 위치가 변했을 때 고양이의 방향을 적당히 수정한다.
            if (Body.LinearVelocity.X > 2f)
                CurDirection = Direction.RIGHT;
            else if (Body.LinearVelocity.X < -2f)
                CurDirection = Direction.LEFT;

            if (_state == State.JUMP) 
            {
                Debug.Assert(!IsMultiGeom, "현재는 multigeom이 아닐때만을 고려해 구현되어 있다.");
                switch (_jumpState)
                {
                    case JumpState.FLY:
                        FlyState st = calculateFlyState();
                        _curFrame = (int)st;
                        if (st == FlyState.Down && _body.LinearVelocity.Y <= 1f)
                            ChangeJumpState(JumpState.LANDED);
                        break;
                }
            }
        }

        public override void BindSimulator(PhysicsSimulator simulator)
        {
            base.BindSimulator(simulator);
            if (IsMultiGeom)
            {
                _bodys = new Body[VertsArray.Length];
                _geoms = new Geom[VertsArray.Length];
                _origins = new Vector2[VertsArray.Length];
                for (int i = 0; i < VertsArray.Length; ++i)
                {
                    Body b;
                    Geom g;
                    Vector2 o;
                    makeBodyGeom(Mass, VertsArray[i], out b, out g, out o);
                    g.RestitutionCoefficient = 0.0f;
                    _bodys[i] = b;
                    _geoms[i] = g;
                    _origins[i] = o;
                }
                
                simulator.Add(_bodys[0]);
                simulator.Add(_geoms[0]);
            }
            else
            {
                makeBodyGeom(Mass, Verts, out _body, out _geom, out _origin);
                simulator.Add(_body);
                simulator.Add(_geom);
            }
        }

        public override void UnbindSimulator()
        {
            base.UnbindSimulator();
            //TODO:: delete code from here (IsMultiGeom -> 없앨것임)
            if (IsMultiGeom)
            {
                for (int i = 0; i < VertsArray.Length; ++i)
                {
                    _simulator.Remove(_bodys[0]);
                    _simulator.Remove(_geoms[0]);
                }
            }
            //to here
            else
            {
                _simulator.Remove(_body);
                _simulator.Remove(_geom);
            }
        }

        /// <summary>
        /// 각 스프라이트 별로 프레임 수를 계산한다. Sprite와 프레임 크기가 모두 주어진 다음에
        /// 불려야 한다. ResourceManager에서 Character를 생성하고 XML Deserialization이 끝나고 나서 꼭 불러주어야 한다.
        /// </summary>
        public void SetNumFrames()
        {
            _numFrames = new int[_sprites.Length];
            for (int i = 0; i < _sprites.Length; ++i)
                _numFrames[i] = _sprites[i].Width / _frameSizes[i].Width;
        }

        public override object Clone()
        {
            //copy this BindSimulator하기 전 상태에서...
            Character c = new Character();
            Substance._clone(this as Substance, c as Substance);
            c.IsCFR = this.IsCFR;
            c.IsMultiGeom = this.IsMultiGeom;
            c.CFRDurations = this.CFRDurations;
            c.FrameSizes = this.FrameSizes;
            c.SpriteAssetNames = this.SpriteAssetNames;
            c.VertsArray = this.VertsArray;
            c.TextureVertsArray = this.TextureVertsArray;
            c.VFRDurations = this.VFRDurations;
            c.SetTextures(this._sprites);
            c.SetNumFrames();
            c.CurState = this.CurState;
            c.CurJumpState = this.CurJumpState;
            c.Speed = this.Speed;
            c.SetInverseTextures(this._inverseSprites);
            c.HasInverse = this.HasInverse;
            return c as object;
        }

        public void SetDatas(CharacterDataSet ds)
        {
            base.SetDatas(ds as Substance.DataSet);
            if (IsMultiGeom)
            {
                foreach (Body b in _bodys)
                {
                    b.Position = ds.Position;
                    b.Rotation = ds.Rotation;
                }
            }
            if (ds.IsVirtualGeom)
            {
                if (IsMultiGeom)
                {
                    foreach (Body b in _bodys)
                        _simulator.Remove(b);
                    foreach (Geom g in _geoms)
                        _simulator.Remove(g);
                }
                else
                {
                    _simulator.Remove(_body);
                    _simulator.Remove(_geom);
                }
            }
            Speed = ds.Speed;
        }

        public CharacterDataSet GetDatas()
        {
            CharacterDataSet ds = new CharacterDataSet();
            _getDatas(ds);
            ds.Speed = Speed;
            return ds;
        }

        /// <summary>
        /// 캐릭터의 Texture를 설정합니다.
        /// </summary>
        /// <param name="sprite"></param>
        /// <param name="sprites"></param>
        public void SetTextures(Texture2D[] sprites)
        {
            _sprites = sprites;
        }
        public void SetInverseTextures(Texture2D[] inverseSprites)
        {
            _inverseSprites = inverseSprites;
        }

        #endregion

        #region Constructors
        public Character()
            : base()
        {
            base.IsFoldable = false;
            _elapsedTime = 0;
            Speed = 200;
            _hasInverse = false;
            InFoldMode = false;
        }

        #endregion

        #region Processor
        public class CharacterReader : ContentTypeReader<Character>
        {
            protected override Character Read(ContentReader input, Character existingInstance)
            {
                Character c = new Character();
                c.Name = input.ReadString();
                c.Mass = input.ReadSingle();
                c.Layer = (LayerOrder)input.ReadInt32();
                c._property = (Properties)input.ReadUInt64();
                c.Kind = (CharacterKind)input.ReadInt32();
                c.SpriteAssetNames = new string[input.ReadInt32()];
                for (int i = 0; i < c.SpriteAssetNames.Length; ++i)
                    c.SpriteAssetNames[i] = input.ReadString();
                Texture2D[] sprites = new Texture2D[c.SpriteAssetNames.Length];
                for (int i = 0; i < c.SpriteAssetNames.Length; ++i)
                    sprites[i] = input.ContentManager.Load<Texture2D>(c.SpriteAssetNames[i]);
                c.SetTextures(sprites);
                c.InverseSpriteAssetNames = new string[input.ReadInt32()];
                for (int i = 0; i < c.InverseSpriteAssetNames.Length; ++i)
                    c.InverseSpriteAssetNames[i] = input.ReadString();
                Texture2D[] inverseSprites = new Texture2D[c.InverseSpriteAssetNames.Length];
                for (int i = 0; i < c.InverseSpriteAssetNames.Length; ++i)
                    inverseSprites[i] = input.ContentManager.Load<Texture2D>(c.InverseSpriteAssetNames[i]);
                c.SetInverseTextures(inverseSprites);
                c.FrameSizes = new Rectangle[input.ReadInt32()];
                for (int i = 0; i < c.FrameSizes.Length; ++i)
                    c.FrameSizes[i] = new Rectangle(0, 0, input.ReadInt32(), input.ReadInt32());
                c.IsCFR = input.ReadBoolean();
                c.CFRDurations = new int[input.ReadInt32()];
                for (int i = 0; i < c.CFRDurations.Length; ++i)
                    c.CFRDurations[i] = input.ReadInt32();
                c.VFRDurations = new int[input.ReadInt32()][];
                for (int i = 0; i < c.VFRDurations.Length; ++i)
                {
                    c.VFRDurations[i] = new int[input.ReadInt32()];
                    for (int j = 0; j < c.VFRDurations[j].Length; ++j)
                    {
                        c.VFRDurations[i][j] = input.ReadInt32();
                    }
                }
                c.IsMultiGeom = input.ReadBoolean();
                Vector2[] v = new Vector2[input.ReadInt32()];
                for (int i = 0; i < v.Length; ++i)
                    v[i] = new Vector2(input.ReadSingle(), input.ReadSingle());
                c.Verts = new Vertices(v);
                c.VertsArray = new Vertices[input.ReadInt32()];
                for (int i = 0; i < c.VertsArray.Length; ++i)
                {
                    c.VertsArray[i] = new Vertices();
                    int count = input.ReadInt32();
                    for (int j = 0; j < count; ++j)
                    {
                        c.VertsArray[i].Add(new Vector2(input.ReadSingle(), input.ReadSingle()));
                    }
                }
                c.TextureVertsArray = new Vertices[input.ReadInt32()];
                for (int i = 0; i < c.TextureVertsArray.Length; ++i)
                {
                    c.TextureVertsArray[i] = new Vertices();
                    int count = input.ReadInt32();
                    for (int j = 0; j < count; ++j)
                    {
                        c.TextureVertsArray[i].Add(new Vector2(input.ReadSingle(), input.ReadSingle()));
                    }
                }
                return c;
            }
        }

        [ContentTypeWriter]
        public class CharacterWriter : ContentTypeWriter<Character>
        {
            protected override void Write(ContentWriter output, Character value)
            {
                output.Write(value.Name);
                output.Write(value.Mass);
                output.Write((int)value.Layer);
                output.Write((UInt64)value._property);
                output.Write((int)value.Kind);
                output.Write(value.SpriteAssetNames.Length);
                for (int i = 0; i < value.SpriteAssetNames.Length; ++i)
                    output.Write(value.SpriteAssetNames[i]);
                output.Write(value.InverseSpriteAssetNames.Length);
                for (int i = 0; i < value.InverseSpriteAssetNames.Length; ++i)
                    output.Write(value.InverseSpriteAssetNames[i]);
                output.Write(value.FrameSizes.Length);
                for (int i = 0; i < value.FrameSizes.Length; ++i)
                {
                    //Frame Size이므로 Width와 Height만 담는다.
                    output.Write(value.FrameSizes[i].Width);
                    output.Write(value.FrameSizes[i].Height);
                }
                output.Write(value.IsCFR);
                output.Write(value.CFRDurations.Length);
                for (int i = 0; i < value.CFRDurations.Length; ++i)
                    output.Write(value.CFRDurations[i]);
                output.Write(value.VFRDurations.Length);
                for (int i = 0; i < value.VFRDurations.Length; ++i)
                {
                    output.Write(value.VFRDurations[i].Length);
                    for (int j = 0; j < value.VFRDurations[i].Length; ++j)
                        output.Write(value.VFRDurations[i][j]);
                }
                output.Write(value.IsMultiGeom);
                output.Write(value.Verts.Count);
                for (int i = 0; i < value.Verts.Count; ++i)
                {
                    output.Write(value.Verts[i].X);
                    output.Write(value.Verts[i].Y);
                }
                output.Write(value.VertsArray.Length);
                for (int i = 0; i < value.VertsArray.Length; ++i)
                {
                    output.Write(value.VertsArray[i].Count);
                    for (int j = 0; j < value.VertsArray[i].Count; ++j)
                        output.Write(value.VertsArray[i][j]);
                }
                output.Write(value.TextureVertsArray.Length);
                for (int i = 0; i < value.TextureVertsArray.Length; ++i)
                {
                    output.Write(value.TextureVertsArray[i].Count);
                    for (int j = 0; j < value.TextureVertsArray[i].Count; ++j)
                    {
                        output.Write(value.TextureVertsArray[i][j].X);
                        output.Write(value.TextureVertsArray[i][j].Y);
                    }
                }
            }
            public override string GetRuntimeReader(TargetPlatform targetPlatform)
            {
                return typeof(Character.CharacterReader).AssemblyQualifiedName;
            }
            public override string GetRuntimeType(TargetPlatform targetPlatform)
            {
                return typeof(Character).AssemblyQualifiedName;
            }
        }
        #endregion
    }
}
